//
//  MQTTClient.m
//  Rinnai
//
//  Created by 朱天聪 on 2018/5/10.
//  Copyright © 2018年 Hadlinks. All rights reserved.
//

#import "SLMqttManager.h"
#import <AWSMobileClient/AWSMobileClient.h>
#import <AWSIoT/AWSIoT.h>

//AWS服务器端点 - AWS后台配置
//static  NSString *const IOT_ENDPOINT = @"https://d0054826m8hql015n9ja-ats.iot.us-east-2.amazonaws.com";
static  NSString *const IOT_ENDPOINT = @"https://a2purlu2igiez4-ats.iot.us-east-2.amazonaws.com";

//AWS身份池id - AWS后台创建
static  NSString *const IDENTITY_POOL_ID = @"us-east-2:b9c600fd-00c0-4393-843f-9f80cb78472a";
//策略名称 - AWS后台配置
static  NSString *const POLICY_NAME = @"bridge_test_policy";
//区域
static  AWSRegionType const AWS_REGION = AWSRegionUSEast2;

@interface SLMqttManager ()


@property (nonatomic, assign)AWSIoTMQTTStatus status;
@property (nonatomic, strong)AWSIoTDataManager *iotDataManager;
@property (nonatomic, strong)AWSIoTManager *iotManager;
@property (nonatomic, strong)AWSIoT *iot;
//订阅的topic
@property (nonatomic,strong) NSMutableDictionary *subedDict;

@property (nonatomic,assign) BOOL isWaitConnected;
@property (nonatomic, strong)NSMutableDictionary *callbacks;
@property (nonatomic, strong)NSMutableDictionary *timeoutDates;
@property (nonatomic, strong)NSMutableDictionary *heartDates;
@property (nonatomic, strong)NSTimer *timer;
@property (nonatomic,copy)SLUpdateFirmwareBlock updateFirmwareBlock;
@property (nonatomic, strong)NSData *firmwareData;//固件包数据
@property (nonatomic, assign)NSInteger packetSize;
@property (nonatomic, assign)NSInteger packetIndex;
@property (nonatomic, assign)NSInteger totalPacket;
@property (nonatomic, strong)NSTimer *checkOnlineTimer;
@property (nonatomic, strong)NSArray *userDeviceList;
@end
@implementation SLMqttManager

+(instancetype)sharedInstance {
    static SLMqttManager *user = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        user = [[SLMqttManager alloc] init];
    });
    return user;
}

//初始化AWS平台参数
- (void)initConifg{
    if (!_callbacks) _callbacks = [NSMutableDictionary new];
    if (!_timeoutDates) _timeoutDates = [NSMutableDictionary new];
    if (!_heartDates) _heartDates = [NSMutableDictionary new];
    if (!self.timer) {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(checkTimeouts) userInfo:nil repeats:YES];
    }
    
    AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWS_REGION identityPoolId:IDENTITY_POOL_ID];
    AWSServiceConfiguration *config = [[AWSServiceConfiguration alloc] initWithRegion:AWS_REGION credentialsProvider:credentialsProvider];
    [AWSServiceManager defaultServiceManager].defaultServiceConfiguration = config;
    _iotManager = [AWSIoTManager defaultIoTManager];
    _iot = [AWSIoT defaultIoT];
    
    NSURL *iotEndPoint = [NSURL URLWithString:IOT_ENDPOINT];
    AWSServiceConfiguration *serverConfig = [[AWSServiceConfiguration alloc] initWithRegion:AWS_REGION
                                                                                   endpoint:[[AWSEndpoint alloc] initWithURL:iotEndPoint]
                                                                        credentialsProvider:credentialsProvider];
    AWSIoTMQTTConfiguration *mqttConfig = [[AWSIoTMQTTConfiguration alloc] initWithKeepAliveTimeInterval:30 baseReconnectTimeInterval:1 minimumConnectionTimeInterval:20 maximumReconnectTimeInterval:128 runLoop:[NSRunLoop currentRunLoop] runLoopMode:NSDefaultRunLoopMode autoResubscribe:YES lastWillAndTestament:[AWSIoTMQTTLastWillAndTestament new]];
    [AWSIoTDataManager registerIoTDataManagerWithConfiguration:serverConfig withMQTTConfiguration:mqttConfig forKey:@"MyIotDataManager"];
    _iotDataManager = [AWSIoTDataManager IoTDataManagerForKey:@"MyIotDataManager"];
    
}

//连接AWS iot平台
-(void)connectAwsMqttServer:(void(^)(BOOL isConnect,NSError *error))finish{
    if (!_iot) {
        [self initConifg];
    }
    [self createCertificateIdAndStoreinNSUserDefaultsOnSuccess:^(NSString *generatedCertificateId) {
        
        [self.iotDataManager connectWithClientId:kUUID cleanSession:YES certificateId:generatedCertificateId statusCallback:^(AWSIoTMQTTStatus status) {
            self->_status = status;
            if (self->_status == AWSIoTMQTTStatusConnected) {
                self->_isConnected = YES;
                NSLog(@"连接MQTT服务器成功,订阅主题topic/magic_bridge");
                NSString *topic = @"topic/magic_bridge";
                [self subscribeTopic:topic messageBlock:^(id data) {
                    [self handleMessageFromMqttServer:data];
                }];
                if (finish) {
                    finish(YES,nil);
                }
                if (self->_checkOnlineTimer) [self->_checkOnlineTimer fire];
            }else{
                self->_isConnected = NO;
            }
        }];
    } onFailure:^(NSError *error) {
        if (finish) {
            finish(NO,error);
        }
        NSLog(@"连接MQTT服务器失败 Received error: %@", error);
    }];
}

-(void)createCertificateIdAndStoreinNSUserDefaultsOnSuccess:(void (^)(NSString *))onSuccess onFailure:(void (^)(NSError *))onFailure {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *csrDictionary = @{
        @"commonName": @"magic_iot_ios",
        @"countryName": @"US",
        @"organizationName": @"MagicLock",
        @"organizationalUnitName": @"Dev"
    };
    [self.iotManager createKeysAndCertificateFromCsr:csrDictionary callback:^(AWSIoTCreateCertificateResponse *response) {
        if (!response) {
            NSError *error = [NSError errorWithDomain:@"No response on iotManager.createKeysAndCertificate" code:-2 userInfo:nil];
            onFailure(error);
            return;
        }
        
        [defaults setObject:response.certificateId forKey:@"certificateId"];
        [defaults setObject:response.certificateArn forKey:@"certificateArn"];
        NSString *certificateId = response.certificateId;
        NSLog(@"response: [%@]", response);
        
        AWSIoTAttachPrincipalPolicyRequest *attachPrincipalPolicyRequest = [AWSIoTAttachPrincipalPolicyRequest new];
        attachPrincipalPolicyRequest.policyName = @"bridge_test_policy";
        attachPrincipalPolicyRequest.principal = response.certificateArn;
        
        // Attach the policy to the certificate
        [[self.iot attachPrincipalPolicy:attachPrincipalPolicyRequest] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) {
            if (task.error) {
                NSLog(@"Failed: [%@]", task.error);
                onFailure(task.error);
            } else {
                NSLog(@"result: [%@]", task.result);
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    if (certificateId) {
                        onSuccess(certificateId);
                    } else {
                        NSError *error = [NSError errorWithDomain:@"Unable to generate certificate id" code:-1 userInfo:nil];
                        onFailure(error);
                    }
                });
            }
            return nil;
        }];
    }];
}

//订阅主题
-(void)subscribeTopic:(NSString *)topic messageBlock:(void(^)(id data))messageBlock{
    if (topic.length == 0) return;
    [_iotDataManager subscribeToTopic:topic QoS:AWSIoTMQTTQoSMessageDeliveryAttemptedAtMostOnce messageCallback:^(NSData * _Nonnull data) {
        NSString *stringValue = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        [self handleMessageFromMqttServer:stringValue];
    }];
}

/*
 取消订阅
 
 @param topic 主题
 */
-(void)unsubscribeTopic:(NSString *)topic{
    if (topic.length == 0) return;
    [_iotDataManager unsubscribeTopic:topic];
}

/*
 发布消息
 */
-(void)sendDataToTopic:(NSString *)topic params:(NSDictionary *)params{
    if (topic.length == 0) return;
    NSString *jsonStr = [ZCTool objectToJson:params];
    [_iotDataManager publishString:jsonStr onTopic:topic QoS:AWSIoTMQTTQoSMessageDeliveryAttemptedAtMostOnce];
}

-(void)registerShadow{
    [self.iotDataManager registerWithShadow:@"ios_app_test" options:@{} eventCallback:^(NSString * _Nonnull name, AWSIoTShadowOperationType operation, AWSIoTShadowOperationStatusType status, NSString * _Nonnull clientToken, NSData * _Nonnull payload) {
        
    } completionCallback:^{
        
    }];
}

//断开MQTT连接
-(void)disConnect{
    [_iotDataManager disconnect];
}

-(void)checkBridgeIsOnlineOnce:(void(^)(BOOL isOnLine))finish{
    [self requestMqttApiWith:@"heartbeat" params:nil finish:^(BOOL isSuccessed, id  _Nonnull result, NSString * _Nonnull code, NSString * _Nonnull message) {
        BOOL isOnLine = [kSTRING(result[@"status"]) isEqualToString:@"0"];
        if (finish) finish(isOnLine);
    }];
}

//检测设备是否在线 - 持续检查
-(void)checkBridgeOnlineStatus:(void(^)(SLLockModel *model,BOOL isOnline))finish{
    
    _userDeviceList = [UserManager getUserDeviceList];
    if (_userDeviceList.count == 0) {
        _heartBlock = nil;
        if (self.checkOnlineTimer && self.checkOnlineTimer.isValid) {
            [self.checkOnlineTimer invalidate];
            _checkOnlineTimer = nil;
        }
        return;
    }
    if (!_checkOnlineTimer) {
        _heartBlock = finish;
        _checkOnlineTimer = [NSTimer scheduledTimerWithTimeInterval:15 target:self selector:@selector(checkBrideStatus) userInfo:nil repeats:YES];
    }
}

-(void)checkBrideStatus{
    if (!self.isConnected) {
        return;
    }
    NSArray *deviceList = [UserManager getUserDeviceList];
    [deviceList enumerateObjectsUsingBlock:^(SLLockModel *model, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([model.deviceCategory isEqualToString:@"2"]) {
            [self requestMqttApiWith:@"heartbeat" params:@{@"deviceNo":model.deviceNo} finish:^(BOOL isSuccessed, id  _Nonnull result, NSString * _Nonnull code, NSString * _Nonnull message) {
                
                BOOL isOnLine = [kSTRING(result[@"status"]) isEqualToString:@"0"];
                NSString *msg = [NSString stringWithFormat:@"网关%@在线状态：%d",model.deviceNo,isOnLine];
                NSLog(@"%@",msg);
                if (self->_heartBlock) self->_heartBlock(model,isOnLine);
            }];
        }
    }];
}

//固件升级
-(void)startUpdateFirmwareWith:(NSData *)firmwareData finish:(SLUpdateFirmwareBlock)finish{
    _updateFirmwareBlock = finish;
    _firmwareData = firmwareData;
    _packetSize = 128;
    _packetIndex = 1;
    _totalPacket = (NSInteger)(_firmwareData.length /_packetSize);
    [self requestMqttApiWith:@"otaConfig" params:@{@"totalSize":@(_firmwareData.length)} finish:^(BOOL isSuccessed, id  _Nonnull result, NSString * _Nonnull code, NSString * _Nonnull message) {
        if (!isSuccessed) {
            self->_updateFirmwareBlock(NO,-1,kLocalString(@"Set config faild"));
            return;
        }
        //总包数升级文件大小/每包数据大小算出
        [self startUploadFirmware];
    }];
}

-(void)startUploadFirmware{
    NSData *data = [self configUploadData];
    NSDictionary *params = @{@"curPacket":@(_packetIndex),
                             @"totalPacket":@(_totalPacket),
                             @"data":data};
    [self requestMqttApiWith:@"otaData" params:params finish:^(BOOL isSuccessed, id  _Nonnull result, NSString * _Nonnull code, NSString * _Nonnull message) {
        if (!isSuccessed) {
            self->_updateFirmwareBlock(NO,-1,kLocalString(@"Upload faild"));
            return;
        }
        if (self->_packetIndex == self->_totalPacket) {//最后一个包传输完成
            [self stopUploadFirmwareData];
        }else{
            self->_packetIndex ++;
            //计算进度
            if (self->_updateFirmwareBlock)self->_updateFirmwareBlock(YES,(CGFloat)self->_packetIndex/self->_totalPacket,kLocalString(@"Upload progress"));
            [self startUploadFirmware];
        }
    }];
}

-(void)stopUploadFirmwareData{
    [self requestMqttApiWith:@"otaEnd" params:nil finish:^(BOOL isSuccessed, id  _Nonnull result, NSString * _Nonnull code, NSString * _Nonnull message) {
        if (!isSuccessed) {
            self->_updateFirmwareBlock(NO,-1,kLocalString(@"Upload faild"));
            return;
        }
        if (self->_updateFirmwareBlock) {
            self->_updateFirmwareBlock(YES,999,kLocalString(@"Upload success"));
        }
    }];
}

-(NSData *)configUploadData{
    //封装数据包
    NSMutableData *paramsData = [[NSMutableData alloc] init];
    //数据（n字节）根据上面0xA1里面数据包的大小 = 当前下标
    NSInteger offset = (_packetIndex - 1)*_packetSize;
    NSRange range;
    if (offset+_packetSize > _firmwareData.length) {
        range = NSMakeRange(offset, _firmwareData.length-_packetSize);
    }else{
        range = NSMakeRange(offset, _packetSize);
    }
    NSLog(@"开始传输固件包 range = %@ _packetIndex = %ld",NSStringFromRange(range),_packetIndex);
    NSData *packetData = [_firmwareData subdataWithRange:range];
    [paramsData appendData:packetData];
    return paramsData;
}


#pragma mark -
#pragma mark - ================<MQTT协议封装>===============
-(void)requestMqttApiWith:(NSString *)cmd params:(NSDictionary *)params finish:(ZCRequiredFinished)finish{
    AWSIoTMQTTStatus status = [_iotDataManager getConnectionStatus];
    if (status == AWSIoTMQTTStatusConnected) {
        [self sendRequestWith:cmd params:params finish:finish];
    }
    else if(status == AWSIoTMQTTStatusConnecting){
        if (finish) finish(NO,@{},@"",kLocalString(@"MQTT server is connecting"));
        [self connectAwsMqttServer:nil];
    }
    else if (status == AWSIoTMQTTStatusDisconnected){
        [self connectAwsMqttServer:^(BOOL isConnect, NSError *error) {
            if (!isConnect) {
                if (finish) finish(NO,@{},@"",error.localizedDescription);
                return;
            }
            [self sendRequestWith:cmd params:params finish:finish];
        }];
    }
    else{
        if (finish) finish(NO,@{},@"",kLocalString(@"MQTT server connect faild"));
        [self connectAwsMqttServer:nil];
    }
}

-(void)sendRequestWith:(NSString *)cmd params:(NSDictionary *)params finish:(ZCRequiredFinished)finish{
    NSString *deviceNo = kSTRING(params[@"deviceNo"]);
    if (deviceNo.length == 0) deviceNo = kCurClickDevice.deviceNo;
    if (deviceNo.length == 0) {
        if (finish) finish(NO,@{},@"",kLocalString(@"No device click"));
        return;
    }
    NSDate *date = [NSDate date];
    NSString *timestamp = [date timestampMillSecond];
    NSMutableDictionary *newParams = [NSMutableDictionary new];
    [newParams setObject:cmd forKey:@"cmd"];
    [newParams setObject:@"appToDevice" forKey:@"direction"];
    [newParams setObject:timestamp forKey:@"timestamp"];
    [newParams setObject:deviceNo forKey:@"deviceNo"];
    [newParams setObject:kUUID forKey:@"clientId"];
    if ([params isKindOfClass:[NSDictionary class]]) {
        [newParams addEntriesFromDictionary:params];
    }
    NSLog(@"发送MQTT消息给设备 %@",newParams);
    NSString *jsonStr = [ZCTool objectToJson:newParams];
    [_iotDataManager publishString:jsonStr onTopic:@"topic/magic_bridge" QoS:AWSIoTMQTTQoSMessageDeliveryAttemptedAtMostOnce];
    timestamp = [NSString stringWithFormat:@"%@",cmd];
    [_callbacks setObject:finish forKey:timestamp];
    if ([cmd isEqualToString:@"heartbeat"]) {
        [_heartDates setObject:date forKey:timestamp];
    }else{
        [_timeoutDates setObject:date forKey:timestamp];
    }
}

-(void)handleMessageFromMqttServer:(NSString *)jsonStr{
    NSDictionary *data = [ZCTool dictionaryWithJsonStr:jsonStr];
    if (![data isKindOfClass:[NSDictionary class]]) {
        return;
    }
    
    NSString *direction = kSTRING(data[@"direction"]);
    if (![direction isEqualToString:@"deviceToApp"]) {
        return;
    }
    NSString *cmd = kSTRING(data[@"cmd"]);
    if (cmd.length == 0) {
        return;
    }
    //网关上线通知
    if ([cmd isEqualToString:@"online"]) {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"kBridgeBecomeOnline" object:@{@"deviceNo":data[@"deviceNo"]} userInfo:nil];
        return;
    }
    
//    NSString *clientId = kSTRING(data[@"clientId"]);
//    if (![clientId isEqualToString:kUUID]) {
//        return;
//    }
    
    NSLog(@"收到MQTT设备回复 %@",data);
    NSString *requestId = [NSString stringWithFormat:@"%@",cmd];
    ZCRequiredFinished callback = _callbacks[requestId];
    NSString *code = kSTRING(data[@"code"]);
    NSString *message = kSTRING(data[@"message"]);
    NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithDictionary:data];
    [result removeObjectForKey:@"direction"];
    [result removeObjectForKey:@"cmd"];
    [result removeObjectForKey:@"code"];
    [result removeObjectForKey:@"clientId"];
    [result removeObjectForKey:@"timestamp"];
    [result removeObjectForKey:@"deviceNo"];
    
    BOOL isSuccess = NO;
    if ([code isEqualToString:@"0"] || [code isEqualToString:@"1"]) {
        isSuccess = YES;
    }
    dispatch_main_async_safe(^{
        if (callback){
            callback(isSuccess,result,code,message);
            [self-> _callbacks removeObjectForKey:requestId];
            if ([cmd isEqualToString:@"heartbeat"]) {
                [self-> _heartDates removeObjectForKey:requestId];
            }else{
                [self-> _timeoutDates removeObjectForKey:requestId];
            }
        }
    });
}

//单次请求超时机制 10s超时
-(void)checkTimeouts{
    NSDate *currentDate = [NSDate date];
    [self.timeoutDates enumerateKeysAndObjectsUsingBlock:^(NSString *requestId,NSDate *requestDate, BOOL * _Nonnull stop) {
        if ([[currentDate timestamp] integerValue] - [[requestDate timestamp] integerValue] >= 10) {
            ZCRequiredFinished callback = _callbacks[requestId];
            if (callback){
                callback(NO,@{},@"",@"request timeout");
                [_callbacks removeObjectForKey:requestId];
                [_timeoutDates removeObjectForKey:requestId];
                
            }
        }
    }];
    
    [self.heartDates enumerateKeysAndObjectsUsingBlock:^(NSString *requestId,NSDate *requestDate, BOOL * _Nonnull stop) {
        if ([[currentDate timestamp] integerValue] - [[requestDate timestamp] integerValue] >= 5) {
            ZCRequiredFinished callback = _callbacks[requestId];
            if (callback){
                callback(NO,@{},@"",@"request timeout");
                [_callbacks removeObjectForKey:requestId];
                [_heartDates removeObjectForKey:requestId];
            }
        }
    }];
}

@end

